package com.moshang.blog.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.moshang.blog.core.constant.CurrentUser;
import com.moshang.blog.core.constant.MySysUser;
import com.moshang.blog.core.utils.MyException;
import com.moshang.blog.core.utils.PageUtil;
import com.moshang.blog.entity.SysUser;
import com.moshang.blog.entity.activity.LeaveOpinion;
import com.moshang.blog.model.TaskVo;
import com.moshang.blog.model.UserLeave;
import com.moshang.blog.service.SysUsersService;
import com.moshang.blog.service.UserLeaveService;
import com.moshang.blog.util.Base64Utils;
import com.moshang.blog.util.RestResponse;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.*;
import org.activiti.engine.delegate.event.ActivitiEventListener;
import org.activiti.engine.delegate.event.ActivitiEventType;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricDetail;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricVariableUpdate;
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.activiti.engine.impl.context.Context;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.HMProcessDiagramGenerator;
import org.activiti.spring.ProcessEngineFactoryBean;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import sun.misc.BASE64Encoder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 请假流程
 *
 * @author xww
 * Created on 2019/7/10.
 */
@Controller
@RequestMapping("/leave")
public class UserLeaveController {
    @Autowired
    private UserLeaveService userLeaveService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private ProcessEngineConfiguration processEngineConfiguration;
    @Autowired
    ProcessEngineFactoryBean processEngine;
    @Autowired
    private SysUsersService sysUsersService;

    private final String prefix = "act/leave/";
    private final String leaveOpinionList = "leaveOpinionList";

    @GetMapping(value = "showLeave")
    public String showUser() {
        return prefix + "leaveList";
    }

    @GetMapping(value = "showLeaveList")
    @ResponseBody
    public PageUtil<UserLeave> showLeaveList( UserLeave userLeave, @RequestParam(value = "page", defaultValue = "1") Integer page,
                                             @RequestParam(value = "limit", defaultValue = "10") Integer limit) {
        PageUtil<UserLeave> pageUtil = new PageUtil<>();
        Integer userId = MySysUser.id();
        userLeave.setUserId(userId);
        QueryWrapper<UserLeave> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        wrapper.orderBy(true,true,"begin_time");

        try {
            IPage<UserLeave> dataPage = userLeaveService.page(new Page<>(page, limit), wrapper);
            for (UserLeave leave : dataPage.getRecords()) {
                ProcessInstance instance = runtimeService.createProcessInstanceQuery()
                        .processInstanceId(leave.getProcessInstanceId()).singleResult();
                //保证运行ing
                if (instance != null) {
                    Task task = this.taskService.createTaskQuery().processInstanceId(leave.getProcessInstanceId()).singleResult();
                    leave.setTaskName(task.getName());
                }
            }
            pageUtil.setCount((int) dataPage.getTotal());
            pageUtil.setData(dataPage.getRecords());
        } catch (MyException e) {
            e.printStackTrace();
        }

        return pageUtil;
    }

    @GetMapping("addLeave")
    public String addLeave() {
        return prefix + "add";
    }

    /**
     * 新建请假
     *
     * @param model
     * @param userLeave
     * @return
     */
    @PostMapping("addLeave")
    @ResponseBody
    public RestResponse addLeave(Model model, UserLeave userLeave) {
        RestResponse restResponse = new RestResponse();
        if (userLeave == null) {
            return RestResponse.failure("获取数据失败");
        }
        userLeave.setDays(3);
        CurrentUser user = MySysUser.currentUser();
        userLeave.setUserId(user.getId());
        userLeave.setUserName(user.getUserName());
        userLeave.setProcessInstanceId("2018");//模拟数据
        userLeave.setStatus("审核中");
        user.setCreateTime(new Date());
        userLeaveService.save(userLeave);
        Map<String, Object> map = new HashMap<>();
        userLeave.setUrlPath("/leave/readOnlyLeave/" + userLeave.getId());
        map.put("baseTask", userLeave);
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("process_leave", map);
        UserLeave userLeave1 = userLeaveService.getById(userLeave.getId());
        userLeave1.setProcessInstanceId(processInstance.getId());
        userLeave1.setUrlPath("/leave/readOnlyLeave/" + userLeave.getId());
        userLeaveService.updateById(userLeave1);
        restResponse.setMessage("请假申请成功");
        restResponse.setSuccess(true);
        return restResponse;
    }
    //只读图片页面
    @GetMapping("shinePics/{processInstanceId}")
    public String shinePics(Model model, @PathVariable String processInstanceId) {
        model.addAttribute("processInstanceId", processInstanceId);
        return prefix+"shinePics";
    }

    @GetMapping("getShineProcImage")
    @ResponseBody
    public String getShineProcImage(HttpServletRequest request, HttpServletResponse resp, String processInstanceId)
            throws IOException {
        JSONObject result = new JSONObject();
        JSONArray shineProImages = new JSONArray();
        BASE64Encoder encoder = new BASE64Encoder();
        InputStream imageStream = generateStream(request, resp, processInstanceId, true);
        if (imageStream != null) {
            String imageCurrentNode = Base64Utils.ioToBase64(imageStream);
            if (StringUtils.isNotBlank(imageCurrentNode)) {
                shineProImages.add(imageCurrentNode);
            }
        }
        InputStream imageNoCurrentStream = generateStream(request, resp, processInstanceId, false);
        if (imageNoCurrentStream != null) {
            String imageNoCurrentNode = Base64Utils.ioToBase64(imageNoCurrentStream);
            if (StringUtils.isNotBlank(imageNoCurrentNode)) {
                shineProImages.add(imageNoCurrentNode);
            }
        }
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Task task = this.taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
        TaskVo taskVo = new TaskVo();
        List<TaskVo> list=new ArrayList<>();
        if (task!=null) {
            Integer userId = Integer.valueOf(task.getAssignee());
            SysUser sysUser = sysUsersService.getById(userId);

            taskVo.setName(task.getName());
            taskVo.setProcessInstanceId(task.getProcessInstanceId());
            taskVo.setProcessDefinitionId(task.getProcessDefinitionId());
            taskVo.setCreateTime( dateFormat.format(task.getCreateTime()));
            taskVo.setUserName(sysUser.getRealName());

        }else {
            taskVo.setName("已结束");
            taskVo.setUserName("无");
            taskVo.setCreateTime(dateFormat.format(new Date()));
        }
        list.add(taskVo);
        result.put("id", UUID.randomUUID().toString());
        result.put("errorNo", 0);
        result.put("images", shineProImages);
        result.put("taskSqu", list);
        return result.toJSONString();
    }
    /**
     * 根据 执行对象id获取审批信息
     *
     * @param model
     * @param processId
     * @return
     */
    @GetMapping("leaveDetail")
    public String leaveDetail( Model model,  @RequestParam("processId") String processId) {
      model.addAttribute("processId",processId);
        return prefix+"leaveDetail";
    }
    @ResponseBody
    @RequestMapping("/showDetail")
    public Map<String,Object> methodx(
            @RequestParam(required=false,defaultValue="1") int page,
            @RequestParam(required=false,defaultValue="15") int limit,
            String processId
    ){

        ProcessInstance instance = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processId).singleResult();
        //保证运行ing
        List<LeaveOpinion> leaveList = null;
        List<HistoricActivityInstance> historicActivityInstanceList = new ArrayList<>();
        if (instance != null) {
            Task task = this.taskService.createTaskQuery().processInstanceId(processId).singleResult();
            Map<String, Object> variables = taskService.getVariables(task.getId());
            Object o = variables.get(leaveOpinionList);
            if (o != null) {
                /*获取历史审核信息*/
                leaveList = (List<LeaveOpinion>) o;
            }
        } else {
            leaveList = new ArrayList<>();
            List<HistoricDetail> list = historyService.createHistoricDetailQuery().
                    processInstanceId(processId).list();
            HistoricVariableUpdate variable = null;
            for (HistoricDetail historicDetail : list) {
                variable = (HistoricVariableUpdate) historicDetail;
                String variableName = variable.getVariableName();
                if (leaveOpinionList.equals(variable.getVariableName())) {
                    leaveList.clear();
                    leaveList.addAll((List<LeaveOpinion>) variable.getValue());
                }
            }
        }
        Map<String,Object> map=new HashMap<String,Object>();
        map.put("code",0);
        map.put("msg","");
        map.put("count",leaveList.size());
        map.put("data",leaveList);
        return map;
    }


    public InputStream generateStream(HttpServletRequest request, HttpServletResponse resp, String processInstanceId, boolean needCurrent) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        HistoricProcessInstance historicProcessInstance =
                historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String processDefinitionId = null;
        List<String> executedActivityIdList = new ArrayList<String>();
        List<String> currentActivityIdList = new ArrayList<>();
        List<HistoricActivityInstance> historicActivityInstanceList = new ArrayList<>();
        if (processInstance != null) {
            processDefinitionId = processInstance.getProcessDefinitionId();
            if (needCurrent) {
                currentActivityIdList = this.runtimeService.getActiveActivityIds(processInstance.getId());
            }
        }
        if (historicProcessInstance != null) {
            processDefinitionId = historicProcessInstance.getProcessDefinitionId();
            historicActivityInstanceList =
                    historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceId().asc().list();
            for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
                executedActivityIdList.add(activityInstance.getActivityId());
            }
        }

        if (StringUtils.isEmpty(processDefinitionId) || executedActivityIdList.isEmpty()) {
            return null;
        }

        //高亮线路id集合
        ProcessDefinitionEntity definitionEntity = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(processDefinitionId);
        List<String> highLightedFlows = getHighLightedFlows(definitionEntity, historicActivityInstanceList);

        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        //List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);
        processEngineConfiguration = processEngine.getProcessEngineConfiguration();
        Context.setProcessEngineConfiguration((ProcessEngineConfigurationImpl) processEngineConfiguration);
        HMProcessDiagramGenerator diagramGenerator = (HMProcessDiagramGenerator) processEngineConfiguration.getProcessDiagramGenerator();
        //List<String> activeIds = this.runtimeService.getActiveActivityIds(processInstance.getId());

        InputStream imageStream = diagramGenerator.generateDiagram(
                bpmnModel, "png",
                executedActivityIdList, highLightedFlows,
                processEngine.getProcessEngineConfiguration().getActivityFontName(),
                processEngine.getProcessEngineConfiguration().getLabelFontName(),
                "宋体",
                null, 1.0, currentActivityIdList);

        return imageStream;
    }

    /**
     * 获取需要高亮的线
     *
     * @param processDefinitionEntity
     * @param historicActivityInstances
     * @return
     */
    private List<String> getHighLightedFlows(
            ProcessDefinitionEntity processDefinitionEntity,
            List<HistoricActivityInstance> historicActivityInstances) {

        List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId
        for (int i = 0; i < historicActivityInstances.size() - 1; i++) {// 对历史流程节点进行遍历
            ActivityImpl activityImpl = processDefinitionEntity
                    .findActivity(historicActivityInstances.get(i)
                            .getActivityId());// 得到节点定义的详细信息
            List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>();// 用以保存后需开始时间相同的节点
            ActivityImpl sameActivityImpl1 = processDefinitionEntity
                    .findActivity(historicActivityInstances.get(i + 1)
                            .getActivityId());
            // 将后面第一个节点放在时间相同节点的集合里
            sameStartTimeNodes.add(sameActivityImpl1);
            for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
                HistoricActivityInstance activityImpl1 = historicActivityInstances
                        .get(j);// 后续第一个节点
                HistoricActivityInstance activityImpl2 = historicActivityInstances
                        .get(j + 1);// 后续第二个节点
                if (activityImpl1.getStartTime().equals(
                        activityImpl2.getStartTime())) {
                    // 如果第一个节点和第二个节点开始时间相同保存
                    ActivityImpl sameActivityImpl2 = processDefinitionEntity
                            .findActivity(activityImpl2.getActivityId());
                    sameStartTimeNodes.add(sameActivityImpl2);
                } else {
                    // 有不相同跳出循环
                    break;
                }
            }
            List<PvmTransition> pvmTransitions = activityImpl
                    .getOutgoingTransitions();// 取出节点的所有出去的线
            for (PvmTransition pvmTransition : pvmTransitions) {
                // 对所有的线进行遍历
                ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition
                        .getDestination();
                // 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
                if (sameStartTimeNodes.contains(pvmActivityImpl)) {
                    highFlows.add(pvmTransition.getId());
                }
            }
        }
        return highFlows;
    }
    @GetMapping("readOnlyLeave/{billId}")
    public String readOnlyLeave(Model model, @PathVariable String billId) {
        UserLeave leave = userLeaveService.getById(billId);
        model.addAttribute("leave", leave);
        return prefix+"update-leave-readonly";
    }
}
